﻿namespace WcfComposition
{
    using System;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Dispatcher;

    /// <summary>
    /// An instance provider utilizing MEF to provide runtime composition
    /// within the WCF service host.
    /// </summary>
    /// <remarks>Implements <see cref="IInstanceProvider"/>.</remarks>
    public class ComposingInstanceProvider : IInstanceProvider
    {
        #region Fields

        private readonly Type serviceType;
        private readonly CompositionContainer container;
        private readonly String contractName;
        
        #endregion

        #region Properties

        /// <summary>
        /// Gets a reference to the current container within context of the 
        /// service environment.
        /// </summary>
        /// <value></value>
        public CompositionContainer Container
        {
            get
            {
                Contract.Ensures( this.container != null );
                return this.container;
            }
        }

        /// <summary>
        /// The type of the top level service the instance provider generates
        /// instances for.
        /// </summary>
        /// <remarks>Requires MEF Export on top level service to match the Type argument passed
        /// for registration in the Service Host.</remarks>
        public Type ServiceType
        {
            get
            {
                Contract.Ensures( this.serviceType != null );
                return this.serviceType;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of the ComposingInstanceProvider
        /// </summary>
        /// <param name="serviceType">A <see cref="Type"/> object.</param>
        /// <param name="container">A <see cref="CompositionContainer"/> object.</param>
        public ComposingInstanceProvider(Type serviceType, CompositionContainer container)
        {
            Contract.Requires( serviceType != null, "serviceType" );
            Contract.Requires( container != null, "container" );

            this.serviceType = serviceType;
            this.container = container;
            this.contractName = AttributedModelServices.GetContractName( serviceType );
        }

        #endregion

        #region IInstanceProvider Members

        /// <summary>
        /// Returns a service instance associated with the specified context.
        /// </summary>
        /// <param name="instanceContext">The associated <see cref="InstanceContext">instance context</see>.</param>
        /// <param name="message">The <see cref="Message">message</see> associated with the instance.</param>
        /// <returns>The provided service <see cref="Object">instance</see>.</returns>
        public virtual object GetInstance( InstanceContext instanceContext, Message message )
        {
            return this.Container.GetExports( this.ServiceType, null, this.contractName ).Single().Value;
        }

        /// <summary>
        /// Returns a service instance associated with the specified context.
        /// </summary>
        /// <param name="instanceContext">The associated <see cref="InstanceContext">instance context</see>.</param>
        /// <returns>The provided service <see cref="Object">instance</see>.</returns>
        public object GetInstance( InstanceContext instanceContext )
        {
            return this.GetInstance( instanceContext, null );
        }

        /// <summary>
        /// Releases an instance associated with the specified context.
        /// </summary>
        /// <param name="instanceContext">The associated <see cref="InstanceContext">instance context</see>.</param>
        /// <param name="instance">The service <see cref="Object">instance</see> to be released.</param>
        public virtual void ReleaseInstance( InstanceContext instanceContext, object instance )
        {
            var disposableInstance = instance as IDisposable;

            if ( disposableInstance != null )
                disposableInstance.Dispose();
        }

        #endregion
    }
}
